﻿using System;
using System.Collections.Generic;


namespace FinQ
{

    /// <summary>
    /// Additional type-specific (non-double) versions of Stats.cs
    /// </summary>
    /// <remarks>
    /// Ultimately may want to include long and float in addition to int and decimal.
    /// Double is the "master" implementation because some of the System.Math functions
    /// like square root are only provided as double. LINQ provides long and float.
    /// 
    /// Not sure what to do about the "nullable" type versions, double?, long?
    /// </remarks>
    static partial class Functional
    {
        

        // sum
        public static int Sum(this IEnumerable<int> seq)
        {
            return Reduce(seq, 0, (x, y) => x + y);
        }

        public static decimal Sum(this IEnumerable<decimal> seq)
        {
            return Reduce(seq, 0M, (x, y) => x + y);
        }

        public static float Sum(this IEnumerable<float> seq)
        {
            return Reduce(seq, 0F, (x, y) => x + y);
        }


        // arithmetic mean
        public static double Avg(this IEnumerable<int> seq)
        {
            int count = seq.Count();
            if (count == 0) return 0;
            return (double)seq.Sum() / count;
        }

        public static decimal Avg(this IEnumerable<decimal> seq)
        {
            int count = seq.Count();
            if (count == 0) return 0;
            return seq.Sum() / count;
        }

        public static float Avg(this IEnumerable<float> seq)
        {
            int count = seq.Count();
            if (count == 0) return 0;
            return seq.Sum() / count;
        }


        // min
        public static int Min(this IEnumerable<int> seq)
        {
            return Reduce(seq, int.MaxValue, (x, y) => (x < y) ? x : y);
        }

        public static decimal Min(this IEnumerable<decimal> seq)
        {
            return Reduce(seq, decimal.MaxValue, (x, y) => (x < y) ? x : y);
        }

        public static float Min(this IEnumerable<float> seq)
        {
            return Reduce(seq, float.MaxValue, (x, y) => (x < y) ? x : y);
        }


        // max
        public static decimal Max(this IEnumerable<int> seq)
        {
            return Reduce(seq, int.MinValue, (x, y) => (x > y) ? x : y);
        }

        public static decimal Max(this IEnumerable<decimal> seq)
        {
            return Reduce(seq, decimal.MinValue, (x, y) => (x > y) ? x : y);
        }

        public static float Max(this IEnumerable<float> seq)
        {
            return Reduce(seq, float.MinValue, (x, y) => (x > y) ? x : y);
        }


        // variance (of population by default)
        public static double Variance(this IEnumerable<int> seq)
        {
            return VarP(seq);
        }

        public static decimal Variance(this IEnumerable<decimal> seq)
        {
            return VarP(seq);
        }

        public static float Variance(this IEnumerable<float> seq)
        {
            return VarP(seq);
        }


        // variance of sample - VarA
        public static double VarA(this IEnumerable<int> seq)
        {
            double avg = seq.Avg();
            int count = seq.Count();
            if (count < 1) return 0;
            return Map(seq, x => Square(x - avg)).Sum() / (count - 1);
        }

        public static decimal VarA(this IEnumerable<decimal> seq)
        {
            decimal avg = seq.Avg();
            int count = seq.Count();
            if (count < 1) return 0;
            return Map(seq, x => Square(x - avg)).Sum() / (count - 1);
        }

        public static float VarA(this IEnumerable<float> seq)
        {
            float avg = seq.Avg();
            int count = seq.Count();
            if (count < 1) return 0;
            return Map(seq, x => Square(x - avg)).Sum() / (count - 1);
        }


        // variance of population - VarP
        public static double VarP(this IEnumerable<int> seq)
        {
            double avg = seq.Avg();
            int count = seq.Count();
            if (count < 1) return 0;
            return Map(seq, x => Square(x - avg)).Sum() / count;
        }

        public static decimal VarP(this IEnumerable<decimal> seq)
        {
            decimal avg = seq.Avg();
            int count = seq.Count();
            if (count < 1) return 0;
            return Map(seq, x => Square(x - avg)).Sum() / count;
        }

        public static float VarP(this IEnumerable<float> seq)
        {
            float avg = seq.Avg();
            int count = seq.Count();
            if (count < 1) return 0;
            return Map(seq, x => Square(x - avg)).Sum() / count;
        }

        // standard deviation (of population by default)
        public static double StdDev(this IEnumerable<int> seq)
        {
            return StdDevP(seq);
        }

        public static decimal StdDev(this IEnumerable<decimal> seq)
        {
            return StdDevP(seq);
        }

        public static float StdDev(this IEnumerable<float> seq)
        {
            return StdDevP(seq);
        }


        // standard deviation (of population)
        public static double StdDevP(this IEnumerable<int> seq)
        {
            return Math.Sqrt(VarP(seq));
        }

        public static decimal StdDevP(this IEnumerable<decimal> seq)
        {
            return (decimal)Math.Sqrt((double)VarP(seq));
        }

        public static float StdDevP(this IEnumerable<float> seq)
        {
            return (float)Math.Sqrt(VarP(seq));
        }


        // standard deviation (of sample)
        public static double StdDevA(this IEnumerable<int> seq)
        {
            return Math.Sqrt(VarA(seq));
        }

        public static decimal StdDevA(this IEnumerable<decimal> seq)
        {
            return (decimal)Math.Sqrt((double)VarA(seq));
        }

        public static float StdDevA(this IEnumerable<float> seq)
        {
            return (float)Math.Sqrt(VarA(seq));
        }

    }
}
